#pragma once
#include <iostream>
#include <unistd.h>
#include <queue>
#include <pthread.h>
#include"lockGuard.hpp"
const int NUM = 5;
template <class T>
class BlockQueue
{
private:
    bool BlockQueueIsFull()
    {
        return bq_.size() == capacity_;
    }
    bool BlockQueueIsEmpty()
    {
        return bq_.size() == 0;
    }

public:
    BlockQueue(int capacity = NUM) : capacity_(capacity) // 构造时就初始化锁和条件变量
    {
        pthread_mutex_init(&mtx_, nullptr);
        pthread_cond_init(&Empty_, nullptr);
        pthread_cond_init(&Full_, nullptr);
    }
    void push(const T &in) // 生产者
    {
        // 访问临界区,申请锁
        lockGuard lockguard(&mtx_);
        // pthread_mutex_lock(&mtx_); //
        while (BlockQueueIsFull())
            pthread_cond_wait(&Full_, &mtx_);// 判断阻塞队列是否满了，满了就在Full_条件变量下等待
        //  1. 先检测临界资源是否满足访问条件
        // pthread_cond_wait:持有锁去进行等待，在调用时，会自动释放锁
        // pthread_cond_wait:在哪里阻塞挂起，下一次就在哪里唤醒，被唤醒的时候还是在临界区
        // 当pthread_cond_wait被唤醒时，会自动帮线程获取锁
        // pthread_cond_wait是一个函数，函数总会有调用失败的时候。存在伪唤醒的情况，使用： while(BlockQueueIsFull()) 进行循环判断
        // 2. 访问临界资源，确认临界资源是100%就绪的
        bq_.push(in);                 // 执行插入
        pthread_cond_signal(&Empty_); // 插入后阻塞队列中有值了，唤醒消费者
        // pthread_mutex_unlock(&mtx_);
    }
    void pop(T *out) // 消费者
    {
        // 访问临界区,申请锁
        // pthread_mutex_lock(&mtx_);
        lockGuard lockguard(&mtx_);
        while (BlockQueueIsEmpty())
            pthread_cond_wait(&Empty_, &mtx_);
        *out = bq_.front();
        bq_.pop();
        pthread_cond_signal(&Full_);
        // pthread_mutex_unlock(&mtx_);
    }
    ~BlockQueue() // 自动调用析构释放锁以及条件变量
    {
        pthread_mutex_destroy(&mtx_);
        pthread_cond_destroy(&Empty_);
        pthread_cond_destroy(&Full_);
    }

public: // 为了方便线程访问，设为公有，正常为封装函数返回私有值
    std::queue<T> bq_;//阻塞队列
    int capacity_;//队列最大容量
    pthread_mutex_t mtx_;//通过互斥锁来保证线程安全
    pthread_cond_t Empty_;//用它来表示bq是否满足empty的状态
    pthread_cond_t Full_;//用它来表示bq是否满足full的状态
};